package no.uib.hplims.views.settings;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.vaadin.appfoundation.authentication.exceptions.PasswordsDoNotMatchException;
import org.vaadin.appfoundation.authentication.exceptions.UsernameExistsException;
import org.vaadin.appfoundation.authentication.util.PasswordUtil;
import org.vaadin.appfoundation.authentication.util.UserUtil;
import org.vaadin.appfoundation.persistence.facade.FacadeFactory;
import org.vaadin.appfoundation.view.AbstractView;

import no.uib.hplims.models.Role;
import no.uib.hplims.models.User;
import no.uib.hplims.tools.Icons;
import no.uib.hplims.tools.SecurityUtil;

import com.vaadin.data.Item;
import com.vaadin.data.Validator;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.ListSelect;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Window.Notification;
import com.vaadin.ui.themes.Runo;

public class AdminUserView extends AbstractView<VerticalLayout> implements
		Button.ClickListener {

	private static final long serialVersionUID = 8839919315987775420L;

	private enum MODE {
		NEW, EDIT
	}

	private MODE editMode = null;

	private final ArrayList<Object> visibleColumnIds = new ArrayList<Object>();
	private final ArrayList<String> visibleColumnLabels = new ArrayList<String>();
	private Table userTable = null;
	private Label tableHeader = null;

	private BeanItemContainer<User> userContainer = null;
	private List<User> allUsers = null;

	private VerticalLayout mainContent = null;
	private VerticalLayout userTableLayout = null;
	private HorizontalLayout hl = null;
	private VerticalLayout editorArea = null;

	private HorizontalLayout buttonMenu = null;
	private Button newUserButton = null;
	private Button editUserButton = null;
	private Button deleteUserButton = null;

	private Label editorAreaHeader = null;
	private TextField nameField = null;
	private TextField emailField = null;
	private TextField usernameField = null;
	private PasswordField passwordField = null;
	private PasswordField repeatPasswordField = null;
	private ListSelect roleSelector = null;

	private Button submitButton = null;
	private Button cancelButton = null;

	private User selectedUser = null;
	private Item tableItem = null;

	public AdminUserView() {
		super(new VerticalLayout());

		mainContent = getContent();
		mainContent.setMargin(true, true, true, true);
		mainContent.setSpacing(true);

		Label header = new Label("Administer users");
		header.setStyleName(Runo.LABEL_H2);
		mainContent.addComponent(header);

		init();
	}

	private void init() {
		allUsers = FacadeFactory.getFacade().list(User.class);

		// Specify which columns are visible
		visibleColumnIds.add("username");
		visibleColumnIds.add("name");
		visibleColumnIds.add("email");
		visibleColumnIds.add("accountLocked");

		// Give those columns readable names
		visibleColumnLabels.add("Username");
		visibleColumnLabels.add("Name");
		visibleColumnLabels.add("Email");
		visibleColumnLabels.add("Account locked?");

		tableHeader = new Label("Registered Users");
		// Create table
		userTable = new Table();
		userTable.setStyleName(Runo.TABLE_SMALL);

		// Set table source
		userContainer = new BeanItemContainer<User>(User.class);
		userContainer.addAll(allUsers);
		userTable.setContainerDataSource(userContainer);

		// Customize table columns
		userTable.setVisibleColumns(visibleColumnIds.toArray());
		userTable.setColumnHeaders(visibleColumnLabels.toArray(new String[0]));

		userTable.setSelectable(true);
		userTable.setMultiSelect(false);
		userTable.setEditable(false);
		userTable.setImmediate(true);
		userTable.setWriteThrough(true);

		// Adding valueChangeListener to listen capture events when users
		// are selected and deselected
		userTable.addListener(new ValueChangeListener() {
			private static final long serialVersionUID = 8662518652763822242L;

			public void valueChange(ValueChangeEvent event) {
				if (userTable.getValue() != userTable.getNullSelectionItemId()) {
					editUserButton.setEnabled(true);
					deleteUserButton.setEnabled(true);
				} else {
					editUserButton.setEnabled(false);
					deleteUserButton.setEnabled(false);
				}
			}
		});

		newUserButton = new Button("New user");
		newUserButton.addListener(this);
		newUserButton.setIcon(Icons.userSettingsIcon);
		editUserButton = new Button("Edit user");
		editUserButton.addListener(this);
		editUserButton.setEnabled(false);
		editUserButton.setIcon(Icons.editIcon16);
		deleteUserButton = new Button("Delete user");
		deleteUserButton.addListener(this);
		deleteUserButton.setEnabled(false);
		deleteUserButton.setIcon(Icons.removeIcon16);
		buttonMenu = new HorizontalLayout();
		buttonMenu.setSpacing(true);
		buttonMenu.addComponent(newUserButton);
		buttonMenu.addComponent(editUserButton);
		buttonMenu.addComponent(deleteUserButton);

		hl = new HorizontalLayout();
		hl.setSpacing(true);

		userTableLayout = new VerticalLayout();
		userTableLayout.addComponent(tableHeader);
		userTableLayout.addComponent(userTable);
		hl.addComponent(userTableLayout);

		mainContent.addComponent(buttonMenu);
		mainContent.addComponent(hl);
	}

	public void activated(Object... params) {

	}

	public void deactivated(Object... params) {
		// TODO Auto-generated method stub

	}

	private boolean createNewUser(String name, String email, String username,
			String password, String verifyPassword) {
		// Try to register user.
		User newUser = null;
		if (SecurityUtil.passwordIsValid(password)) {
			try {
				newUser = SecurityUtil.createUser(username, password,
						verifyPassword, name, email,
						(Collection<Role>) roleSelector.getValue());
				userContainer.addBean(newUser);
			} catch (UsernameExistsException e) {
				this.usernameField
						.setComponentError(new Validator.InvalidValueException(
								"Username already exists"));
			} catch (PasswordsDoNotMatchException e) {
				passwordsDoNotMatchException();
			}
		}

		// Return
		return newUser != null;
	}

	private boolean editExistingUser() {
		if (selectedUser != null) {

			String name = (String) nameField.getValue();
			String email = (String) emailField.getValue();
			String username = (String) usernameField.getValue();
			String password = (String) passwordField.getValue();
			String repeatPassword = (String) repeatPasswordField.getValue();

			try {
				// Change password?
				if ((!password.isEmpty() || !repeatPassword.isEmpty())) {
					// Is password valid?
					if (SecurityUtil.passwordIsValid(password)) {
						SecurityUtil.adminChangePassword(selectedUser,
								password, repeatPassword);
					} else {
						return false;
					}
				}
				if (!username.isEmpty()) {

					SecurityUtil.changeUsername(selectedUser, username);

					tableItem.getItemProperty("username").setValue(username);
				}
				if (!email.isEmpty()) {
					tableItem.getItemProperty("email").setValue(
							emailField.getValue());
				}
				if (!name.isEmpty()) {
					tableItem.getItemProperty("name").setValue(
							nameField.getValue());
				}

				UserUtil.storeUser(selectedUser);
			} catch (PasswordsDoNotMatchException e) {
				passwordsDoNotMatchException();
				return false;
			} catch (UsernameExistsException e) {
				usernameField
						.setComponentError(new Validator.InvalidValueException(
								"Username already taken"));
				return false;
			}
			return true;
		}
		return false;
	}

	private void initEditorArea(User user) {
		selectedUser = user;
		if (editorArea == null) {
			editorArea = new VerticalLayout();
			editorArea.setVisible(false);

			editorAreaHeader = new Label();

			nameField = new TextField("Name");
			emailField = new TextField("Email");
			usernameField = new TextField("Username");
			
			passwordField = new PasswordField("Password");
			passwordField.setImmediate(true);
			repeatPasswordField = new PasswordField("Repeat Password");
			repeatPasswordField.setImmediate(true);
			List<Validator> validators = PasswordUtil.getValidators();
			for (Validator val : validators) {
				passwordField.addValidator(val);
				repeatPasswordField.addValidator(val);
			}
			
			List<Role> roles = FacadeFactory.getFacade().list(Role.class);
			roleSelector = new ListSelect("Roles");
			for (Role r : roles) {
				roleSelector.addItem(r);
			}
			roleSelector.setRows(3);
			roleSelector.setNullSelectionAllowed(false);
			roleSelector.setMultiSelect(true);
			roleSelector.setImmediate(true);

			submitButton = new Button("Create User");
			submitButton.addListener(this);
			cancelButton = new Button("Cancel");
			cancelButton.addListener(this);

			editorArea.addComponent(editorAreaHeader);
			editorArea.addComponent(nameField);
			editorArea.addComponent(emailField);
			editorArea.addComponent(usernameField);
			editorArea.addComponent(passwordField);
			editorArea.addComponent(repeatPasswordField);
			editorArea.addComponent(roleSelector);

			HorizontalLayout footer = new HorizontalLayout();
			footer.addComponent(submitButton);
			footer.addComponent(cancelButton);
			editorArea.addComponent(footer);

			hl.addComponent(editorArea);
		}
		resetFields();
		if (editMode == MODE.NEW) {
			editorAreaHeader.setCaption("New User:");

			submitButton.setCaption("Create User");

			nameField.setRequired(true);
			emailField.setRequired(true);
			usernameField.setRequired(true);
			passwordField.setRequired(true);
			repeatPasswordField.setRequired(true);

		} else if (editMode == MODE.EDIT) {
			if (user != null) {
				editorAreaHeader.setCaption("Edit User Details:");

				submitButton.setCaption("Update");

				tableItem = userTable.getItem(userTable.getValue());

				nameField.setValue(tableItem.getItemProperty("name").toString());
				emailField.setValue(tableItem.getItemProperty("email").toString());
				usernameField.setValue(tableItem.getItemProperty("username").toString());

				nameField.setRequired(false);
				emailField.setRequired(false);
				usernameField.setRequired(false);
				passwordField.setRequired(false);
				repeatPasswordField.setRequired(false);
			}
		}
		editorArea.setVisible(true);
	}

	private void removeErrors() {
		this.nameField.setComponentError(null);
		this.emailField.setComponentError(null);
		this.usernameField.setComponentError(null);
		this.passwordField.setComponentError(null);
		this.repeatPasswordField.setComponentError(null);
	}

	private void resetFields() {
		this.nameField.setValue("");
		this.emailField.setValue("");
		this.usernameField.setValue("");
		this.passwordField.setValue("");
		this.repeatPasswordField.setValue("");
	}

	public void buttonClick(ClickEvent event) {
		if (event.getButton() == newUserButton) {
			editMode = MODE.NEW;
			initEditorArea(null);
		} else if (event.getButton() == editUserButton) {
			editMode = MODE.EDIT;
			initEditorArea((User) userTable.getValue());
		} else if (event.getButton() == submitButton) {
			removeErrors();
			if (editMode == MODE.NEW) {
				if (createNewUser((String) nameField.getValue(),
						(String) emailField.getValue(),
						(String) usernameField.getValue(),
						(String) passwordField.getValue(),
						(String) repeatPasswordField.getValue())) {
					editorArea.setVisible(false);
					getWindow().showNotification("Successful!",
							"User created successfully.",
							Notification.TYPE_TRAY_NOTIFICATION);
				}
			} else if (editMode == MODE.EDIT) {
				if (editExistingUser()) {
					editorArea.setVisible(false);
					getWindow().showNotification("Successful!",
							"User edited successfully.",
							Notification.TYPE_TRAY_NOTIFICATION);
				}
			}
		} else if (event.getButton() == cancelButton) {
			editorArea.setVisible(false);
		} else if (event.getButton() == deleteUserButton) {
			deleteUser((User) userTable.getValue());
		}
	}

	private void deleteUser(User user) {
		// remove user from table
		userTable.removeItem(user);
		// delete user from database
		SecurityUtil.deleteUser(user);
		getWindow().showNotification("Successfully deleted user:",
				"Deleted " + user.getUsername(),
				Notification.TYPE_TRAY_NOTIFICATION);
	}

	private void passwordsDoNotMatchException() {
		this.passwordField
				.setComponentError(new Validator.InvalidValueException(
						"Passwords do not match"));
		this.repeatPasswordField
				.setComponentError(new Validator.InvalidValueException(
						"Passwords do not match"));
	}
}
